﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.Linq;
using System.Net.Mail;
using System.Resources;
using System.Text;
using Altairis.MailToolkit.Configuration;

namespace Altairis.MailToolkit {

    /// <summary>
    /// Class for sending templated e-mail messages
    /// </summary>
    public static class Mailer {
        private static MailToolkitConfigurationSection config;
        private static ResourceManager templateResourceManager;

        /// <summary>
        /// Initializes the <see cref="Mailer"/> class.
        /// </summary>
        static Mailer() {
            // Read general configuration
            config = ConfigurationManager.GetSection("altairis.mailToolkit") as MailToolkitConfigurationSection;
            if (config == null) throw new ConfigurationErrorsException("The 'altairis.mailToolkit' configuration section is missing.");

            // Prepare resource manager
            var resourceType = Type.GetType(config.TemplateResource.Type);
            if (resourceType == null) throw new ConfigurationErrorsException("Type specified in 'altairis.mailToolkit/templateResource/@type' is invalid or cannot be found.");
            templateResourceManager = new ResourceManager(resourceType);
        }

        #region SendTemplatedMessage method

        /// <summary>
        /// Sends templated message to single recipient.
        /// </summary>
        /// <param name="recipient">The recipient address.</param>
        /// <param name="templateName">Name of the template.</param>
        /// <param name="args">The formatting arguments for the template.</param>
        public static void SendTemplatedMessage(string recipient, string templateName, params object[] args) {
            SendTemplatedMessages(null, null, new MailAddress[] { new MailAddress(recipient) }, templateName, args);
        }

        /// <summary>
        /// Sends templated message to single recipient.
        /// </summary>
        /// <param name="recipient">The recipient address.</param>
        /// <param name="templateName">Name of the template.</param>
        /// <param name="args">The formatting arguments for the template.</param>
        public static void SendTemplatedMessage(MailAddress recipient, string templateName, params object[] args) {
            SendTemplatedMessages(null, null, new MailAddress[] { recipient }, templateName, args);
        }

        /// <summary>
        /// Sends templated message to single recipient.
        /// </summary>
        /// <param name="replyTo">The address where replies are to be sent.</param>
        /// <param name="recipient">The recipient address.</param>
        /// <param name="templateName">Name of the template.</param>
        /// <param name="args">The formatting arguments for the template.</param>
        public static void SendTemplatedMessage(MailAddress replyTo, MailAddress recipient, string templateName, params object[] args) {
            SendTemplatedMessages(null, replyTo, new MailAddress[] { recipient }, templateName, args);
        }

        /// <summary>
        /// Sends templated message to single recipient.
        /// </summary>
        /// <param name="culture">The message culture used for localization.</param>
        /// <param name="recipient">The recipient address.</param>
        /// <param name="templateName">Name of the template.</param>
        /// <param name="args">The formatting arguments for the template.</param>
        public static void SendTemplatedMessage(CultureInfo culture, string recipient, string templateName, params object[] args) {
            SendTemplatedMessages(culture, null, new MailAddress[] { new MailAddress(recipient) }, templateName, args);
        }

        /// <summary>
        /// Sends templated message to single recipient.
        /// </summary>
        /// <param name="culture">The message culture used for localization.</param>
        /// <param name="recipient">The recipient address.</param>
        /// <param name="templateName">Name of the template.</param>
        /// <param name="args">The formatting arguments for the template.</param>
        public static void SendTemplatedMessage(CultureInfo culture, MailAddress recipient, string templateName, params object[] args) {
            SendTemplatedMessages(culture, null, new MailAddress[] { recipient }, templateName, args);
        }

        /// <summary>
        /// Sends templated message to single recipient.
        /// </summary>
        /// <param name="culture">The message culture used for localization.</param>
        /// <param name="replyTo">The address where replies are to be sent.</param>
        /// <param name="recipient">The recipient address.</param>
        /// <param name="templateName">Name of the template.</param>
        /// <param name="args">The formatting arguments for the template.</param>
        public static void SendTemplatedMessage(CultureInfo culture, MailAddress replyTo, MailAddress recipient, string templateName, params object[] args) {
            SendTemplatedMessages(culture, replyTo, new MailAddress[] { recipient }, templateName, args);
        }

        /// <summary>
        /// Sends templated message to single recipient.
        /// </summary>
        /// <param name="providerName">Name of the transport provider.</param>
        /// <param name="culture">The message culture used for localization.</param>
        /// <param name="replyTo">The address where replies are to be sent.</param>
        /// <param name="recipient">The recipient address.</param>
        /// <param name="templateName">Name of the template.</param>
        /// <param name="args">The formatting arguments for the template.</param>
        public static void SendTemplatedMessage(string providerName, CultureInfo culture, MailAddress replyTo, MailAddress recipient, string templateName, params object[] args) {
            SendTemplatedMessages(providerName, culture, replyTo, new MailAddress[] { recipient }, templateName, args);
        }

        #endregion SendTemplatedMessage method

        #region SendTemplatedMessages method overloads

        /// <summary>
        /// Sends templated messages to multiple recipients.
        /// </summary>
        /// <param name="recipients">The recipient addresses.</param>
        /// <param name="templateName">Name of the template.</param>
        /// <param name="args">The formatting arguments for the template.</param>
        public static void SendTemplatedMessages(IEnumerable<string> recipients, string templateName, params object[] args) {
            var recList = new MailAddressCollection();
            recList.Add(string.Join(",", recipients.ToArray()));
            SendTemplatedMessages(null, null, recList, templateName, args);
        }

        /// <summary>
        /// Sends templated messages to multiple recipients.
        /// </summary>
        /// <param name="recipients">The recipient addresses.</param>
        /// <param name="templateName">Name of the template.</param>
        /// <param name="args">The formatting arguments for the template.</param>
        public static void SendTemplatedMessages(IEnumerable<MailAddress> recipients, string templateName, params object[] args) {
            SendTemplatedMessages(null, null, recipients, templateName, args);
        }

        /// <summary>
        /// Sends templated messages to multiple recipients.
        /// </summary>
        /// <param name="replyTo">The address where replies are to be sent.</param>
        /// <param name="recipients">The recipient addresses.</param>
        /// <param name="templateName">Name of the template.</param>
        /// <param name="args">The formatting arguments for the template.</param>
        public static void SendTemplatedMessages(MailAddress replyTo, IEnumerable<MailAddress> recipients, string templateName, params object[] args) {
            SendTemplatedMessages(null, replyTo, recipients, templateName, args);
        }

        public static void SendTemplatedMessages(CultureInfo culture, IEnumerable<string> recipients, string templateName, params object[] args) {
            var recList = new MailAddressCollection();
            recList.Add(string.Join(",", recipients.ToArray()));
            SendTemplatedMessages(culture, null, recList, templateName, args);
        }

        /// <summary>
        /// Sends templated messages to multiple recipients.
        /// </summary>
        /// <param name="culture">The message culture used for localization.</param>
        /// <param name="recipients">The recipient addresses.</param>
        /// <param name="templateName">Name of the template.</param>
        /// <param name="args">The formatting arguments for the template.</param>
        public static void SendTemplatedMessages(CultureInfo culture, IEnumerable<MailAddress> recipients, string templateName, params object[] args) {
            SendTemplatedMessages(culture, null, recipients, templateName, args);
        }

        #endregion SendTemplatedMessages method overloads

        /// <summary>
        /// Sends templated messages to multiple recipients.
        /// </summary>
        /// <param name="culture">The message culture used for localization.</param>
        /// <param name="replyTo">The address where replies are to be sent.</param>
        /// <param name="recipients">The recipient addresses.</param>
        /// <param name="templateName">Name of the template.</param>
        /// <param name="args">The formatting arguments for the template.</param>
        ///
        public static void SendTemplatedMessages(CultureInfo culture, MailAddress replyTo, IEnumerable<MailAddress> recipients, string templateName, params object[] args) {
            SendTemplatedMessages(null, culture, replyTo, recipients, templateName, args);
        }

        /// <summary>
        /// Sends the templated messages.
        /// </summary>
        /// <param name="providerName">Name of the transport provider.</param>
        /// <param name="culture">The message culture used for localization.</param>
        /// <param name="replyTo">The address where replies are to be sent.</param>
        /// <param name="recipients">The recipient addresses.</param>
        /// <param name="templateName">Name of the template.</param>
        /// <param name="args">The formatting arguments for the template.</param>
        /// <exception cref="System.ArgumentNullException">templateName</exception>
        /// <exception cref="System.ArgumentException">Value cannot be null or empty string.;templateName</exception>
        /// <exception cref="System.ArgumentOutOfRangeException">
        /// templateName;Resource corresponding with template was not found.
        /// or
        /// templateName;Resource corresponding with template was not found.
        /// or
        /// providerName;Provider not found
        /// </exception>
        public static void SendTemplatedMessages(string providerName, CultureInfo culture, MailAddress replyTo, IEnumerable<MailAddress> recipients, string templateName, params object[] args) {
            // Validate arguments
            if (templateName == null) throw new ArgumentNullException("templateName");
            if (string.IsNullOrEmpty(templateName)) throw new ArgumentException("Value cannot be null or empty string.", "templateName");

            // Use current culture if not specified otherwise
            if (culture == null) culture = System.Threading.Thread.CurrentThread.CurrentUICulture;

            // Prepare message subject
            var subject = templateResourceManager.GetString(templateName + config.TemplateResource.SubjectFormatSuffix, culture);
            if (string.IsNullOrEmpty(subject)) throw new ArgumentOutOfRangeException("templateName", templateName + config.TemplateResource.SubjectFormatSuffix, "Resource corresponding with template was not found.");
            subject = string.Format(subject, args);
            var subjectFormat = templateResourceManager.GetString(config.TemplateResource.SubjectFormatKey, culture);
            if (!string.IsNullOrEmpty(subjectFormat)) subject = string.Format(subjectFormat, subject);

            // Prepare message plain text body
            var plainTextBody = templateResourceManager.GetString(templateName + config.TemplateResource.BodyFormatSuffix, culture);
            if (string.IsNullOrEmpty(plainTextBody)) throw new ArgumentOutOfRangeException("templateName", templateName + config.TemplateResource.BodyFormatSuffix, "Resource corresponding with template was not found.");
            plainTextBody = string.Format(plainTextBody, args);
            var plainTextBodyFormat = templateResourceManager.GetString(config.TemplateResource.BodyFormatKey, culture);
            if (!string.IsNullOrEmpty(plainTextBodyFormat)) plainTextBody = string.Format(plainTextBodyFormat, plainTextBody);

            // Prepare message HTML text body
            var htmlBody = templateResourceManager.GetString(templateName + config.TemplateResource.HtmlFormatSuffix, culture);
            if (!string.IsNullOrEmpty(htmlBody)) {
                htmlBody = string.Format(htmlBody, args);
                var htmlBodyFormat = templateResourceManager.GetString(config.TemplateResource.HtmlFormatKey, culture);
                if (!string.IsNullOrEmpty(htmlBodyFormat)) htmlBody = string.Format(htmlBodyFormat, htmlBody);
            }

            using (MailMessage msg = new MailMessage()) {
                // Prepare mail message content
                if (string.IsNullOrEmpty(htmlBody)) {
                    // Message is plain text only
                    msg.Body = plainTextBody;
                    msg.BodyEncoding = Encoding.GetEncoding(config.Encoding.Body);
                }
                else {
                    // Message is plain text and HTML
                    msg.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(plainTextBody, msg.BodyEncoding, "text/plain"));
                    msg.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(htmlBody, msg.BodyEncoding, "text/html"));
                }

                // Prepare mail message headers
                msg.SubjectEncoding = Encoding.GetEncoding(config.Encoding.Subject);
                msg.From = config.From.MailAddress;
                msg.Subject = subject;
                if (config.Headers.AddMailer) msg.Headers.Add("X-Mailer", Signature);
                if (config.Headers.AddReceived && System.Web.HttpContext.Current != null) {
                    msg.Headers.Add("Received", string.Format("from {0} ({1}) by {2} ({3}) via HTTP",
                        System.Web.HttpContext.Current.Request.UserHostAddress, // 0
                        System.Web.HttpContext.Current.Request.UserHostName,    // 1
                        System.Web.HttpContext.Current.Request.Url.Host,        // 2
                        System.Web.HttpContext.Current.Server.MachineName));    // 3
                }

                // Configure reply-to header
                if (replyTo != null) {
                    msg.ReplyToList.Add(replyTo);
                }
                else if (config.ReplyTo.MailAddress != null) {
                    msg.ReplyToList.Add(config.ReplyTo.MailAddress);
                }

                // Configure sender header
                if (config.Sender.MailAddress != null) msg.Sender = config.Sender.MailAddress;

                // Send messages
                foreach (var recipient in recipients) {
                    msg.To.Clear();
                    msg.To.Add(recipient);
                    if (string.IsNullOrEmpty(providerName)) {
                        // Send using default provider
                        Altairis.MailToolkit.Transport.TransportManager.SendMessage(msg);
                    }
                    else {
                        // Send using specified provider
                        var p = Transport.TransportManager.Providers[providerName];
                        if (p == null) throw new ArgumentOutOfRangeException("providerName", "Provider not found");
                        p.SendMessage(msg);
                    }
                }
            }
        }

        /// <summary>
        /// Gets the mailer class signature for X-Mailer header.
        /// </summary>
        /// <value>The mailer class signature.</value>
        public static string Signature {
            get {
                return string.Format("Altairis.MailToolkit/{0:4} (http://altairismailtoolkit.codeplex.com)",
                    System.Reflection.Assembly.GetExecutingAssembly().GetName().Version);
            }
        }
    }
}